package com.itheima.demo5;

import java.io.*;
import java.net.Socket;
import java.util.Properties;


/**
 * 目标：掌握使用Properties类读取属性文件中的键值对信息。
 *      Java里面万物皆对象，所以属性文件当然也是看做一种对象，当然也可以通过一个类来代表属性文件，Properties就是代表属性文件的，Properties的对象就可以代表一个具体的属性文件
 *  Properties：
 *      是一个Map集合（键值对集合），但是我们一般不会当集合使用。
 *      核心作用：Properties是用来代表属性文件的，通过Properties可以读写属性文件里的内容。
 *
 *使用Properties读取属性文件里的键值对数据
 *      构造器
 *  public Properties()     用于构建Properties集合对象（空容器）
 *      常用方法
 *  public void load(InputStream is)    通过字节输入流，读取属性文件里的键值对数据
 *  public void load(Reader reader)     通过字符输入流，读取属性文件里的键值对数据
 *  public String getProperty(String key)       根据键获取值(其实就是get方法的效果)    补充：文件中读和写都是字符串的形式，该方法能保证都是在通过字符串进行操作
 *  public Set<String> stringPropertyNames()    获取全部键的集合（其实就是ketSet方法的效果）
 *
 * 使用Properties把键值对数据写出到属性文件里去
 *  public Object setProperty(String key, String value)     保存键值对数据到Properties对象中去。
 *  public void store(OutputStream os, String comments)     把键值对数据，通过字节输出流写出到属性文件里去，store方法的第二个参数是声明一些注释，也会保存在属性文件中
 *  public void store(Writer w, String comments)            把键值对数据，通过字符输出流写出到属性文件里去，store方法的第二个参数是声明一些注释，也会保存在属性文件中
 *
 *
 * 注意
 * File对象既可以代表文件、也可以代表文件夹。
 * File封装的对象仅仅是一个路径名，这个路径可以是存在的，也允许是不存在的。
 *
 *  构造器                                         说明
 *public File(String pathname)              根据文件路径创建文件对象
 *public File(String parent, String child)  根据父路径和子路径名字创建文件对象
 *public File(File  parent, String child)   根据父路径对应文件对象和子路径名字创建文件对象
 */
public class ServerReaderThread extends Thread{
    //弹幕：客户端只对接一个服务端当然不用多线程，但是服务端要对接处理多个用户的请求需要用多线程
    //接这个socket
    private Socket socket;
    public ServerReaderThread(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        // 处理的信息：注册 登录。
        //任何管道随时都可能给服务端发注册或者登录的信息，所以服务端需要能够区分注册或者登录的信息，
        // 所以需要客户端发个信号过来，所以客户端用DataOutputStream数据流，因为这个可以写数据类型
        try {
            // 1、得到数据输入流
            //有异常用try catch捕获，因为客户端那边可能会有关闭的，然后导致这里读数据就会出异常，
            // 所以意思就是这里可以做追踪客户端下线的逻辑
            DataInputStream dis = new DataInputStream(socket.getInputStream());

            //不只是只接收一次注册，所以需要加上死循环
            while (true) {
                // 2、读消息类型
                int type = dis.readInt();
                // 3、区别消息，不同处理
                switch (type) {
                    case 1:
                        // 注册      和客户端一样，这里也要写一个服务端的注册方法
                        register();
                        break;
                    case 2:
                        // 登录
                        login();
                }
            }
        } catch (Exception e) {
            // 离线逻辑
            System.out.println("有人离线了~~");
        }
    }

    private void register() throws Exception {
        //所以要一个数据输入流把客户端的注册信息收过来，这个数据输入流就是服务端的socket，这个socket是我们线程已经收到的
        DataInputStream dis = new DataInputStream(socket.getInputStream());     //有异常直接抛出
        //要接两次数据：
        String loginName = dis.readUTF();
        String passWord = dis.readUTF();

        // 开始做判断，登录名是否重复的问题。
        //我们现在没有学数据库，所以是把账户的登录名和密码存在文件里，然后对文件内容进行判断是否有重复的登录名，没有重复就存入到文件中
        //问题来了，那么用什么文件去存储这个登录名和密码。用户名和密码属于键值对，所以用properties属性文件比较好
        //所以需要指定一段相对路径，去存放properties属性文件。这个是属于服务端整体的公用的路径，肯定要设置为常量，所以放在服务端
        String result = "注册成功";
        if(exist(loginName)) {
            //返回true，说明属性文件存在，且包含相同的用户名
            result = "注册失败，您注册的登录名称已被占用！";
        }else {
            // 保存登录名和密码
            Properties properties = new Properties();
            properties.setProperty(loginName, passWord);    //保存键值对数据到Properties对象中去。

            //把键值对数据，通过字符输出流写出到属性文件里去。
            //但是还要考虑每一次注册的用户名和密码在保存到属性文件中的时候，不会覆盖上一次的内容，所以要做追加。store方法的第二个参数是声明一些注释，也会保存在属性文件中
            properties.store(new FileWriter(Server.USERS_FILE_PATH, true), "");
        }

        //用数据输出流响应结果回去
        DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
        dos.writeUTF(result);
        dos.flush();
    }

    private void login() throws Exception {
        DataInputStream dis = new DataInputStream(socket.getInputStream());     //有异常直接抛出
        String loginName = dis.readUTF();
        String passWord = dis.readUTF();

        //登陆之前，先判断是否存在属性文件
        String result = "登录成功";
        File file = new File(Server.USERS_FILE_PATH);
        if(file.exists()){
            //存在属性文件，进入下一个判断：
            // 提取全部的登录名，看是否存在当前这个登录名
            Properties properties = new Properties();
            properties.load(new FileReader(file));

            // 校验登录名：根据登录名获取密码。properties文件是一个Map集合（键值对集合）
            //getProperty方法根据键获取值(其实就是get方法的效果)
            String OKpwd = properties.getProperty(loginName);
            if(OKpwd == null) {
                //根据键取到的值是null，那说明不存在这个登录名
                result = "登录名称错误";
            }else {
                //说明登录名是存在的，取到了登录名为键对应的值
                //根据取到的值校验密码
                if(!OKpwd.equals(passWord)){
                    result = "密码错误！";
                }
            }
        }else {
            //不存在属性文件：
            result = "当前无任何用户信息，无法登录！";
        }

        //用数据输出流响应result回去即可
        DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
        dos.writeUTF(result);
        dos.flush();
    }

    //独立方法来判断属性文件是否存在，如果存在则提取属性文件内容来判断是否存在重复的用户名
    public boolean exist(String loginName) throws Exception {
        File file = new File(Server.USERS_FILE_PATH);
        if(file.exists()){
            //文件存在，所以需要提取全部的登录名，看是否存在当前这个登录名
            Properties properties = new Properties();
            properties.load(new FileReader(file));          //读取属性文件里的键值对数据，有异常直接抛出
            //properties此时有了所有的键值对包括用户名，然后做判断是否包含用户名
            if(properties.containsKey(loginName)) {
                //说明包含相同的用户名
                return true;
            }
        }
        //如果属性文件不存在，返回false。 属性文件存在，且不包含相同的用户名，也返回false。
        //所以返回false的都可以保存用户名和密码
        return false;
    }

}
